$stats = Hash.new
$debug = true

class Debuggable
  
  attr_accessor :deb_name, :deb_flag
  
  def deb(msg)
    if @deb_flag
      puts @deb_name+":"+msg 
    end
  end
  
end

def debug( msg )
  if $debug
    puts "common_lib: "+msg
  end
end

def assert_positiv(name, val, margin=0)
  if val <= margin
    raise Exception, "assertion error, parameter "+name+" must be positive, value seen "+val.to_s+", using margin "+margin.to_s
  end
end

def make_block_component ( name, dimX, dimY, dimZ, material, price=-1 ) 
  if material == nil
    raise Exception, "material parameter is required"
  end
  component=$model.definitions[name] 
  if component != nil 
    puts "componennt "+name+" alreday exists"
  else
    $model.start_operation "building component "+name
    component = $model.definitions.add(name) ;  
    make_block( component, dimX, dimY, dimZ, material )
    set_component_attributes( component, dimX, dimY, dimZ, material, price )
    $model.commit_operation
  end
  return name
end

def make_block_component2 ( name_prefix, dim_x, dim_y, dim_z, material, price=-1 )
  debug( "name_prefix #{name_prefix}, dim_x #{dim_x}, dim_y #{dim_y}, dim_z #{dim_z}, material #{material}, price #{price}" )
  if material == nil; raise Exception, "material parameter is required"; end
  assert_positiv( 'dim_x', dim_x, 0.01.mm )  
  assert_positiv( 'dim_y', dim_y, 0.01.mm )  
  assert_positiv( 'dim_z', dim_z, 0.01.mm )  
  
  name = name_prefix+"_"+material.name+"_"+dim_to_s(dim_x)+"_"+dim_to_s(dim_y)+"_"+dim_to_s(dim_z)
  component=$model.definitions[name] 
  if component != nil 
    puts "componennt "+name+" alreday exists"
  else
    $model.start_operation "building component "+name
    component = $model.definitions.add(name) ;  
    make_block2( component, dim_x, dim_y, dim_z, material )
    set_component_attributes( component, dim_x, dim_y, dim_z, material, price )
    $model.commit_operation
  end
  return name
end

def set_component_attributes(component, dimX, dimY, dimZ, material, price=-1)
    volumen_m3 = dimX.to_m*dimY.to_m*dimZ.to_m
    component.set_attribute "physical_properties", "volumen", volumen_m3
    component.set_attribute "physical_properties", "dimX", dimX
    component.set_attribute "physical_properties", "dimY", dimY
    component.set_attribute "physical_properties", "dimZ", dimZ
    component.set_attribute "physical_properties", "area", 2*dimX.to_m*dimY.to_m+2*dimX.to_m*dimZ.to_m+2*dimY.to_m*dimZ.to_m
    if price >= 0
      component.set_attribute "meta", "price", price
    else
      component.set_attribute "meta", "price", calculate_vol_price(material, volumen_m3)
    end
end

def calculate_vol_price( material, volumen_m3 )
  vol_price_m3 = $material_prices[material.name]
  if vol_price_m3 == nil
    raise Exception, "voluminal price for material "+material.name+" not configured"
  end
  return vol_price_m3*volumen_m3
end

def make_block ( parent, dimX, dimY, dimZ, material )
   
  points = Array.new ;
  points[0] = ORIGIN ;
  points[1] = [dimX, 0 , 0] ;
  points[2] = [dimX, dimY, 0] ;
  points[3] = [0 , dimY, 0] ; 
  
  new_face = parent.entities.add_face points
  new_face.material = material
  new_face.pushpull dimZ
  return new_face
  
end

# makes block starting at Z=0 level
def make_block2 ( parent, dimX, dimY, dimZ, material )
   
  points = Array.new ;
  points[0] = [0, 0, 0];
  points[1] = [dimX, 0 , 0] ;
  points[2] = [dimX, dimY, 0] ;
  points[3] = [0 , dimY, 0] ; 
  
  new_face = parent.entities.add_face points
  new_face.reverse! # reverse normal vector to allow correct extrusion 
  new_face.material = material
  new_face.pushpull dimZ # pushpull argument must be positive
  return new_face
  
end


def dim_to_s ( dimension )
  return dimension.to_l.to_s.gsub(/,0mm/, '')
end

def min( a, b)
  if a < b
    return a
  else
    return b
  end
end

def make_joist ( name_prefix, width, height, length )
  
  name = name_prefix+"_"+dim_to_s(width)+"x"+dim_to_s(height)+"x"+dim_to_s(length)
  
  component=$model.definitions[name] 
  
  if component != nil 
    puts "componennt "+name+" alreday exists"
  else
    
    $model.start_operation name

    # some metadata
    #component.set_attribute "meta", "price", 1

    # set some constant parameters, taken from http://www.finnforest.de/produkte/finnjoist/technischedaten/Pages/Default.aspx
    width = 89.mm
    belt_height = 39.mm
    
    web_width = 10.mm
    web_cut_out_depth = 15.mm
    web_height = height - 2*belt_height + 2*web_cut_out_depth

    #
    # 1) make belt
    #
    points = Array.new
    index=0
    points[index] = ORIGIN
    points[index+=1] = [width, 0 , 0]
    points[index+=1] = [width, belt_height, 0]
    # create web cutting
    points[index+=1] = [width/2+web_width/2, belt_height, 0]
    points[index+=1] = [width/2+web_width/4, belt_height-web_cut_out_depth, 0]
    points[index+=1] = [width/2-web_width/4, belt_height-web_cut_out_depth, 0]
    points[index+=1] = [width/2-web_width/2, belt_height, 0]
    points[index+=1] = [0 , belt_height, 0]
    
    belt_component = $model.definitions.add(name+"_belt_part");
    face = belt_component.entities.add_face points
    face.material = $materials['wood_1']
    face.pushpull length
    set_component_attributes(belt_component, width, belt_height, length, face.material)
    
    #
    # 2) make web part
    #
    points = Array.new
    index=0
    # create web edge going into cutting in belt
    points[index] = [ 0, web_cut_out_depth, 0]
    points[index+=1] = [ web_width/4, 0 , 0]
    points[index+=1] = [ (web_width/4)*3, 0, 0]
    points[index+=1] = [ web_width, web_cut_out_depth, 0]
    # create web side
    points[index+=1] = [ web_width, web_height-web_cut_out_depth, 0]
    # create second edge
    points[index+=1] = [ (web_width/4)*3, web_height, 0]
    points[index+=1] = [ web_width/4, web_height, 0]
    points[index+=1] = [ 0, web_height-web_cut_out_depth, 0]
    
    web_half_part = $model.definitions.add(name+"_web_part");
    face = web_half_part.entities.add_face points
    face.material = $materials['osb_1']
    face.pushpull length
    set_component_attributes(web_half_part, web_width, web_height, length, face.material)

    #
    # 3) combine together
    #
    component = $model.definitions.add(name);
    
    # first belt
    tr = Geom::Transformation.new
    component.entities.add_instance $model.definitions[name+"_belt_part"], tr
    # first web part
    tr = Geom::Transformation.translation [ width/2 - web_width/2, belt_height-web_cut_out_depth, 0 ]
    component.entities.add_instance $model.definitions[name+"_web_part"], tr
    # second belt part
    rot = Geom::Transformation.rotation ORIGIN, [0,0,1], Math::PI
    tr = Geom::Transformation.translation [ width/2 + width/2, 2*belt_height+web_height-2*web_cut_out_depth, 0 ]
    component.entities.add_instance $model.definitions[name+"_belt_part"], tr*rot
    
    $model.commit_operation
    
  end
  return name  
end

def test_joist
  name = make_joist("joist", 89.mm, 400.mm, 5000.mm)
  puts "testing \""+name+"\" jois"
  tr = Geom::Transformation.new
  $entities.add_instance( $model.definitions[name], tr )
end

def load_materials
  # load materials
  files = Dir.glob( $project_home+File::Separator+"img"+File::Separator+"*.jpg" )
  puts "loading "+files.length.to_s+" materials"
  files.each { |file| 
    puts "creating material from file #{file}";
    material_name = file.gsub /.*\//, ''
    material_name = material_name.gsub /\.jpg/, ''
    puts "material name "+material_name
    material = $materials.add material_name
    material.texture = file
  }
end

# make home, grid_x is count of grid-steps to size home's inner living space
def make_home( grid_x, grid_y, floors)
  make_floor(grid_x, grid_y)

  for f in 0..floors-1
    trans = Geom::Transformation.translation [ 0, 0, f * Floor_height ] 
    $entities.add_instance( $model.definitions['floor'], trans )
  end
end

def print_stats( floor_square )
  puts "registered entities:"
  $model.definitions.each do |definition|
    price = definition.get_attribute('meta', 'price')
    if price == nil
      price = "undefined"
    end

    volumen = definition.get_attribute('physical_properties', 'volumen')
    if volumen == nil
      volumen = "undefined"
    end
    
    puts "entity "+definition.name+" volumen "+ volumen.to_s+ ", price "+price.to_s
  end
  puts ""
  
  gather_stats( 0, $entities )
  total_cost = dump_stats
  
  puts "total floor square m2: "+floor_square.to_s
  if floor_square > 0
    puts "per m2 living area: "+ (total_cost/floor_square).to_s + " €/m2"
  end
end

def dump_stats
  total_cost=0
  $stats.keys.each do |entity_name|
    price = $model.definitions[entity_name].get_attribute('meta', 'price')
    cnt=$stats[entity_name]
    cost= cnt * price
    total_cost += cost
    puts "entity \""+entity_name+"\" used "+cnt.to_s+" times, cost "+cost.to_s+" EUR"
  end
  puts "Total cost "+total_cost.to_s+" €";
  return total_cost
end

def check_and_register(entity, price)
    if $stats[entity.definition.name].nil?
      $stats[entity.definition.name]=1;
    else
      $stats[entity.definition.name] = $stats[entity.definition.name]+1
    end
end

def gather_stats( level, entity )
  # puts "analysing "+entity.class.name
  if entity.class == Sketchup::ComponentInstance
    # check if lowest level component with defined price
    price = entity.definition.get_attribute('meta', 'price')
    if price != nil
      check_and_register( entity, price )
    else
      gather_stats( level+1, entity.definition.entities)
    end
  elsif entity.class.name =~ /Sketchup::Group/
    # check if lowest level component with defined price
    price = entity.get_attribute('meta', 'price')
    if price != nil
      check_and_register( entity, price )
    else
      gather_stats( level+1, entity.entities)
    end
  elsif entity.class == Sketchup::Entities
    entity.each do |inst|
      gather_stats(level, inst)
    end
  elsif entity.class == Sketchup::Edge
    raise Exception, "going to deep, Edge instance can not be analysed, please set price on parent entity"
  else 
    raise Exception, "unknown class "+entity.class.name
  end
end
